/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package umis2.nivel;

import java.awt.Point;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.TreeSet;

/* Tener una bandera que indique cuando ha sido modificado un elemento,
 * asi no tener que recorrer todos los atributos de todos los objetos.
 *
 * Debe de funcionar a través de RMI. Se crea la instancia en una sola computadora
 * y todas las demas obtienen un proxy para ese objeto.
 *
 */

/**
 * Sincroniza los estados entre los diferentes niveles de los jugadores
 *
 * @author Alex
 */
public class Sincronizador {

    private Nivel estado;
    private ServerSocket server;
    private ArrayList<Socket> clientes;
    private ArrayList<ObjectInputStream> in;
    private ArrayList<ObjectOutputStream> out;
    private final Object clientesCandado = new Object();


    // Considerar la posibilidad de que solo se actualice aquello que ha cambiado, al menos
    // en la parte que se envía

    private Sincronizador() {
        try {
            server = new ServerSocket(2424);
            clientes = new ArrayList<Socket>();
            in = new ArrayList<ObjectInputStream>();
            out = new ArrayList<ObjectOutputStream>();
            estado = new Nivel();
            estado.setPersonajes(new TreeSet<Personaje>());
            new Thread(new SincronizadorListener(server)).start();
            new Thread(new SincronizadorViewer()).start();
        } catch (IOException ex) { ex.printStackTrace(); }

    }

    public static Sincronizador getInstance() {
        return SincronizadorHolder.INSTANCE;
    }

    private static class SincronizadorHolder {
        private static final Sincronizador INSTANCE = new Sincronizador();
    }


    /** Recibe el nivel del cual tomara la información para actualizar el Nivel global.
        Este metodo se utilizaría en una implementacion RMI **/
    public void actualizar(Nivel nivel) {
        if (estado == null) estado = nivel;

    }



    /** Hilo que escucha las peticiones de los clientes **/
    private class SincronizadorListener implements Runnable {

        private ServerSocket server;

        public SincronizadorListener (ServerSocket s) { server = s; }

        public void run() {
            // Lanzar un hilo que este checando los sockets, y cuando haya algo que actualizar lo realizara
            while (true) {
                try {
                    System.out.println ("Sincronizador - Esperando una peticion");
                    Socket s = server.accept();
                    System.out.println ("Sincronizador - Peticion recibida");

                    synchronized (clientesCandado) { 
                        out.add(new ObjectOutputStream(s.getOutputStream()));
                        in.add(new ObjectInputStream(s.getInputStream()));
                        clientes.add(s);
                        System.out.println ("Sincronizador - peticion atendida" + clientes.size() + "-" + out.size() + "-" + in.size());
                    }
                    String nombre = (out.size()-1) + "";
                    estado.addPersonaje(crearJugador(nombre));
                    out.get(out.size()-1).writeUTF(nombre);
                    out.get(out.size()-1).flush();
                    // System.out.println ("Nombre dado");
                    // Contesta diciendo que numero de jugador es
                } catch (IOException ex) { ex.printStackTrace(); }
            }
        }

        public Jugador crearJugador(String nombre) {
            Jugador j = new Jugador(nombre);
            j.setPosicion(new Point(300,300));
            return j;

        }
    }

    private class SincronizadorViewer implements Runnable {

        public void run() {
            System.out.println ("Viewer - Run start");
            while (true) {
                try {
                    for (int i = 0, l = in.size(); i < l; i++) {
                        System.out.println ("Viewer - Dentro del ciclo");
                        ObjectInputStream o = in.get(i);
                        if (o.available() <= 0) continue;
                        System.out.println ("Viewer - Informacion disponible " + o.available());
                        String nombre = o.readUTF();
                        Nivel n = (Nivel)o.readObject();
                        for (Personaje p : n.getPersonajes()) {
                            if (!(p instanceof Personaje)) continue;
                            Jugador j = (Jugador)p;
                            if (!(j.getNombre().equals(nombre))) continue;
                            // encontramos al jugador que mando esta actualizacion
                            for (Personaje q : estado.getPersonajes()) {
                                if (!(q instanceof Personaje)) continue;
                                Jugador k = (Jugador) p;
                                if (!(k.getNombre().equals(nombre))) continue;
                                // Encontramos al jugador en el estado
                                k.setPosicion(j.getPosicion());
                            }
                        }

                        //Ahora vamos a regresarle el nivel
                        System.out.println ("Viewer - Por escribir el objeto");
                        out.get(i).writeObject(estado);
                        out.get(i).flush();
                        System.out.println ("Viewer - Objeto escrito");

                    }
                } catch (Exception ex) { ex.printStackTrace(); }
            }
        }
    }

    public static void main (String [] args) {
        Sincronizador s = Sincronizador.getInstance();
//
//        ProxySync c = new ProxySync();
//        c.conectar();
//        c = new ProxySync();
//        c.conectar();
//        c = new ProxySync();
//        c.conectar();
    }
    
 }
